
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define page_map_file "/proc/self/pagemap"
#define PFN_MASK ((((uint64_t)1) << 55) - 1)
#define PFN_PRESENT_FLAG (((uint64_t)1) << 63)

int mem_addr_vir2phy(unsigned long vir, unsigned long* phy)
{
    int fd;
    int page_size = getpagesize();
    unsigned long vir_page_idx = vir / page_size;
    unsigned long pfn_item_offset = vir_page_idx * sizeof(uint64_t);
    uint64_t pfn_item;

    fd = open(page_map_file, O_RDONLY);
    if (fd < 0) {
        printf("open %s failed", page_map_file);
        return -1;
    }

    if ((off_t)-1 == lseek(fd, pfn_item_offset, SEEK_SET)) {
        printf("lseek %s failed", page_map_file);
        return -1;
    }

    if (sizeof(uint64_t) != read(fd, &pfn_item, sizeof(uint64_t))) {
        printf("read %s failed", page_map_file);
        return -1;
    }

    if (0 == (pfn_item & PFN_PRESENT_FLAG)) {
        printf("page is not present");
        return -1;
    }
    printf("page_size: %lx, item: %lx\n", page_size, pfn_item);

    *phy = (pfn_item & PFN_MASK) * page_size + vir % page_size;
    return 0;
}

/* 

如果担心vir地址对应的页面不在内存中，可以在调用mem_addr_vir2phy之前，先访问一下此地址。

例如， int  a=*(int *)(void *)vir;

如果担心Linux的swap功能将进程的页面交换到硬盘上从而导致页面的物理地址变化，可以关闭swap功能。

下面两个C库函数可以阻止Linux将当前进程的部分或全部页面交换到硬盘上。

       int mlock(const void *addr, size_t len);
       int mlockall(int flags);
 */

int main(int argc, char const* argv[])
{
    mlockall(MCL_CURRENT | MCL_FUTURE);

    unsigned long* phy;
    static int a;
    unsigned long* b = malloc(64);
    mem_addr_vir2phy(b, &phy);

    printf("va->pa: %p -> %p\n", b, phy);
    return 0;
}
